package com.lipaluma.field.validator.builder;

import java.util.Arrays;
import java.util.Collection;
import java.util.Date;

import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.mapper.FieldValidated;
import com.lipaluma.field.mapper.builder.FieldMapperBuilder;
import com.lipaluma.field.validator.Validator;
import com.lipaluma.field.validator.ValidatorFactory;
import com.lipaluma.field.validator.impl.Enum.EnumConstantValidator;
import com.lipaluma.field.validator.impl.Enum.EnumExcludedOfValidator;
import com.lipaluma.field.validator.impl.Enum.EnumIncludedInValidator;
import com.lipaluma.field.validator.impl.bool.BooleanConstantValidator;
import com.lipaluma.field.validator.impl.collections.CollectionSizeValidator;
import com.lipaluma.field.validator.impl.date.AfterDateValidator;
import com.lipaluma.field.validator.impl.date.BeforeDateValidator;
import com.lipaluma.field.validator.impl.date.DateConstantValidator;
import com.lipaluma.field.validator.impl.date.DateExcludedOfValidator;
import com.lipaluma.field.validator.impl.date.DateIncludedInValidator;
import com.lipaluma.field.validator.impl.numeric.DConstantValidator;
import com.lipaluma.field.validator.impl.numeric.DExcludedOfValidator;
import com.lipaluma.field.validator.impl.numeric.DIncludedInValidator;
import com.lipaluma.field.validator.impl.numeric.DMinMaxValidator;
import com.lipaluma.field.validator.impl.numeric.LConstantValidator;
import com.lipaluma.field.validator.impl.numeric.LExcludedOfValidator;
import com.lipaluma.field.validator.impl.numeric.LIncludedInValidator;
import com.lipaluma.field.validator.impl.numeric.LMinMaxValidator;
import com.lipaluma.field.validator.impl.string.StringConstantValidator;
import com.lipaluma.field.validator.impl.string.StringContainsValidator;
import com.lipaluma.field.validator.impl.string.StringEndsWithValidator;
import com.lipaluma.field.validator.impl.string.StringExcludedOfValidator;
import com.lipaluma.field.validator.impl.string.StringIncludedInValidator;
import com.lipaluma.field.validator.impl.string.StringLengthValidator;
import com.lipaluma.field.validator.impl.string.StringMatchesValidator;
import com.lipaluma.field.validator.impl.string.StringNotContainsValidator;
import com.lipaluma.field.validator.impl.string.StringNotEndsWithValidator;
import com.lipaluma.field.validator.impl.string.StringNotStartsWithValidator;
import com.lipaluma.field.validator.impl.string.StringStartsWithValidator;
import com.lipaluma.field.validator.strategies.LengthStrategy;

public class ValidatorBuilder {

	private FieldMapperBuilder parent;
	private FieldValidated mapper;

	public ValidatorBuilder(FieldMapperBuilder parent, FieldValidated mapper) {
		this.parent = parent;
		this.mapper = mapper;
	}

	public ValidatorBuilder required() {
		mapper.required(true);
		return this;
	}

	public ValidatorBuilder unique() {
		Validator validator = ValidatorFactory.getValidatorByFunction(mapper.getField().getType(), ValidatorFactory.VALIDATION_UNIQUE_FUNCTION);
		if(validator == null)
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'unique' is not applicable on type : "+mapper.getField().getType());			
		mapper.addNewValidator(validator);
		return this;
	}

	public ValidatorBuilder constant() {
		Validator validator = ValidatorFactory.getValidatorByFunction(mapper.getField().getType(), ValidatorFactory.VALIDATION_CONSTANT_FUNCTION);
		mapper.addNewValidator(validator);
		if(validator == null)
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable on type : "+mapper.getField().getType());			
		return this;		
	}
	public ValidatorBuilder constant(Enum<?> value) {
		if(!mapper.getField().getType().isEnum())
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable with an enum value. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new EnumConstantValidator(value));
		return this;				
	}
	public ValidatorBuilder constant(boolean value) {
		if(!boolean.class.equals(mapper.getField().getType()) && !Boolean.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable with a boolean value. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new BooleanConstantValidator(value));
		return this;				
	}
	public ValidatorBuilder constant(int value) {
		if(	!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable with an integer value. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new LConstantValidator(value));
		return this;				
	}
	public ValidatorBuilder constant(long value) {
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable with a long value. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new LConstantValidator(value));
		return this;
	}
	public ValidatorBuilder constant(double value) {
		if(	!double.class.equals(mapper.getField().getType()) && !Double.class.equals(mapper.getField().getType()) &&
			!float.class.equals(mapper.getField().getType()) && !Float.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable with a double value. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new DConstantValidator(value));
		return this;
	}
	public ValidatorBuilder constant(String value){
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable with a string value. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringConstantValidator(value));
		return this;
	}
	public ValidatorBuilder constant(Date value){
		if(	!Date.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'constant' is not applicable with a date value. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new DateConstantValidator(value));
		return this;
	}
//	FromParserValidatorBuilder constant(DateTime value);

	public ValidatorBuilder startsWith(String value) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'starts with' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringStartsWithValidator(value));
		return this;
	}
	public ValidatorBuilder endsWith(String value) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'ends with' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringEndsWithValidator(value));
		return this;
	}
	public ValidatorBuilder notStartsWith(String value) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'not starts with' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringNotStartsWithValidator(value));
		return this;
	}
	public ValidatorBuilder notStartsWithAtLeastOne(String... values) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'not starts with' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringNotStartsWithValidator(Arrays.asList(values), true));
		return this;
	}
	public ValidatorBuilder notEndsWith(String... values) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'not ends with' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringNotEndsWithValidator(values));
		return this;
	}
	public ValidatorBuilder notEndsWithAtLeastOne(String... values) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'not ends with' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringNotEndsWithValidator(Arrays.asList(values), true));
		return this;
	}
	public ValidatorBuilder notEmpty(String value) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'not empty' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringNotEndsWithValidator(value));
		return this;
	}
	public ValidatorBuilder contains(String... values) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'contains' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringContainsValidator(values));
		return this;
	}
	public ValidatorBuilder containsAtLeastOne(String... values) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'contains' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringContainsValidator(Arrays.asList(values), true));
		return this;
	}
	public ValidatorBuilder notContains(String... values) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'contains' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringNotContainsValidator(values));
		return this;
	}
	public ValidatorBuilder notContainsAtLeastOne(String... values) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'contains' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringNotContainsValidator(Arrays.asList(values), true));
		return this;
	}
	public ValidatorBuilder matches(String pattern) {
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'matches' is not applicable for the type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringMatchesValidator(pattern));
		return this;
	}

	
	public ValidatorBuilder includedIn(long... values) {
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'included in' is not applicable with long values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new LIncludedInValidator(values));
		return this;		
	}
	public ValidatorBuilder includedIn(int... values) {
		if(	!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'included in' is not applicable with integer values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new LIncludedInValidator(values));
		return this;		
	}
	public ValidatorBuilder includedIn(double... values){
		if(	!double.class.equals(mapper.getField().getType()) && !Double.class.equals(mapper.getField().getType()) &&
			!float.class.equals(mapper.getField().getType()) && !Float.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'included in' is not applicable with double values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new DIncludedInValidator(values));
		return this;
	}
	public ValidatorBuilder includedIn(String... values){
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'included in' is not applicable with string values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringIncludedInValidator(values));
		return this;
		
	}
	public ValidatorBuilder includedIn(Date... values){
		if(	!Date.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'included in' is not applicable with date values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new DateIncludedInValidator(values));
		return this;
	}
	public ValidatorBuilder includedIn(Enum<?>... values){
		if(!mapper.getField().getType().isEnum())
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'included in' is not applicable with enum values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new EnumIncludedInValidator(values));
		return this;				
	}

	public ValidatorBuilder excludedOf(long... values){
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'excluded of' is not applicable with long values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new LExcludedOfValidator(values));
		return this;		
	}
	public ValidatorBuilder excludedOf(int... values){
		if(	!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'excluded of' is not applicable with integer values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new LExcludedOfValidator(values));
		return this;		
	}
	public ValidatorBuilder excludedOf(double... values){
		if(	!double.class.equals(mapper.getField().getType()) && !Double.class.equals(mapper.getField().getType()) &&
			!float.class.equals(mapper.getField().getType()) && !Float.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'excluded of' is not applicable with double values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new DExcludedOfValidator(values));
		return this;
	}
	public ValidatorBuilder excludedOf(String... values){
		if(	!String.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'excluded of' is not applicable with string values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new StringExcludedOfValidator(values));
		return this;
	}
	public ValidatorBuilder excludedOf(Date... values){
		if(	!Date.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'excluded of' is not applicable with date values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new DateExcludedOfValidator(values));
		return this;
	}
	public ValidatorBuilder excludedOf(Enum<?>... values){
		if(!mapper.getField().getType().isEnum())
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'excluded of' is not applicable with enum values. Expected type : "+mapper.getField().getType());
		mapper.addNewValidator(new EnumExcludedOfValidator(values));
		return this;				
	}
	public ValidatorBuilder length(int length){
		if(String.class.equals(mapper.getField().getType())){
			mapper.addNewValidator(new StringLengthValidator(length));
			return this;
		}
		if(Collection.class.isAssignableFrom(mapper.getField().getClass()) && !mapper.getField().getType().isArray()){
			mapper.addNewValidator(new CollectionSizeValidator(length));
			return this;				
		}
		throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'length' is not applicable for type : "+mapper.getField().getType());
	}
	public ValidatorBuilder length(int length, LengthStrategy strategy){
		if(String.class.equals(mapper.getField().getType())){
			mapper.addNewValidator(new StringLengthValidator(length, strategy));
			return this;
		}
		if(Collection.class.isAssignableFrom(mapper.getField().getClass()) && !mapper.getField().getType().isArray()){
			mapper.addNewValidator(new CollectionSizeValidator(length, strategy));
			return this;				
		}
		throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'length' is not applicable for type : "+mapper.getField().getType());
	}

	public ValidatorBuilder min(long min){
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()) &&
			!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new LMinMaxValidator(min, null));
		return this;
	}
	public ValidatorBuilder min(int min){
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()) &&
			!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new LMinMaxValidator(min, null));
		return this;
	}
	public ValidatorBuilder min(double min){
		if(!double.class.equals(mapper.getField().getType()) && !Double.class.equals(mapper.getField().getType()) &&
			!float.class.equals(mapper.getField().getType()) && !Float.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new DMinMaxValidator(min, null));
		return this;				
	}
	public ValidatorBuilder max(long max){
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()) &&
			!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new LMinMaxValidator(null, max));
		return this;
	}
	public ValidatorBuilder max(int max){
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()) &&
			!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new LMinMaxValidator(null, max));
		return this;
	}
	public ValidatorBuilder max(double max){
		if(!double.class.equals(mapper.getField().getType()) && !Double.class.equals(mapper.getField().getType()) &&
			!float.class.equals(mapper.getField().getType()) && !Float.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new DMinMaxValidator(null, max));
		return this;				
	}
	public ValidatorBuilder onRange(long min, long max){
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()) &&
			!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
			!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new LMinMaxValidator(min, max));
		return this;
	}
	public ValidatorBuilder onRange(int min, int max){
		if(	!long.class.equals(mapper.getField().getType()) && !Long.class.equals(mapper.getField().getType()) &&
				!int.class.equals(mapper.getField().getType()) && !Integer.class.equals(mapper.getField().getType()) &&
				!short.class.equals(mapper.getField().getType()) && !Short.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new LMinMaxValidator(min, max));
		return this;
	}
	public ValidatorBuilder onRange(double min, double max){
		if(!double.class.equals(mapper.getField().getType()) && !Double.class.equals(mapper.getField().getType()) &&
			!float.class.equals(mapper.getField().getType()) && !Float.class.equals(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'min and max' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new DMinMaxValidator(min, max));
		return this;				
	}
	public ValidatorBuilder isAfter(Date date){
		if(!Date.class.isAssignableFrom(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'after' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new AfterDateValidator(date));
		return this;				
	}
	public ValidatorBuilder isBefore(Date date){
		if(!Date.class.isAssignableFrom(mapper.getField().getType()))
			throw new CsvParserException("Error on setting validation of field '"+mapper.getField().getName()+"' : the validation 'after' is not applicable for type : "+mapper.getField().getType());
		mapper.addNewValidator(new BeforeDateValidator(date));
		return this;				
	}
	
	public FieldMapperBuilder end(){
		return parent;
	}

}
